using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using Roslyn.Compilers;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;

namespace NUMP4CS
{
    /// <summary>
    /// traverses through all attribute types inside of project and serializes 
    /// they according to ecore metamodel
    /// </summary>
    public class ExtractAttributesDeclaration
    {
        /// <summary>
        /// Root ecore element
        /// </summary>
        private CSharpModel csharpModel = new CSharpModel();


        /// <summary>
        /// extract attribute type declaration
        /// </summary>
        /// <param name="classDeclarationSyntax"></param>
        /// <param name="typeSymbol"></param>
        private void ExtractAttributeDeclaration(INamedTypeSymbol typeSymbol)
        {

            AttributeTypeDeclaration attTypeDecl = new AttributeTypeDeclaration();
            attTypeDecl.name = typeSymbol.Name;
            attTypeDecl.Modifiers.AddRange(typeSymbol.ToModelAccessibility());
            ExtractConstructorDeclaration(typeSymbol, attTypeDecl);
            ExtractAttributeData(typeSymbol, attTypeDecl);
            ExtractAttributeTypeProps(typeSymbol, attTypeDecl);

            csharpModel.getNamespace(typeSymbol.ContainingNamespace
                    .ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)).AttributeTypes.Add(attTypeDecl);
        }

        private void ExtractAttributeTypeProps(INamedTypeSymbol typeSymbol, AttributeTypeDeclaration attTypeDecl)
        {
            var props = typeSymbol.GetMembers().OfType<PropertySymbol>().
                Where(s => s.DeclaredAccessibility == Roslyn.Compilers.CSharp.Accessibility.Public)
                .ToArray<PropertySymbol>();
            foreach (var prop in props)
            {
                attTypeDecl.Properties.Add(new PropertyDeclaration()
                {
                    name = prop.Name,
                    type1 = prop.Type.ToString()
                });
            }
        }

        private void ExtractAttributeData(INamedTypeSymbol typeSymbol, 
            AttributeTypeDeclaration attTypeDecl)
        {
            var usages = typeSymbol.GetAttributes().OfType<AttributeData>();
            if (usages != null)
            {
                foreach (var usage in usages)
                {
                    if ("UsageAttribute".Equals(usage.AttributeClass.Name))
                    {
                        ExtractUsageAttributeData(attTypeDecl, usage);
                    }
                    else
                    {
                        ExtractOtherAttributeData(attTypeDecl, usage);
                    }
                }
            }
        }

        private void ExtractConstructorDeclaration(INamedTypeSymbol typeSymbol, 
            AttributeTypeDeclaration attTypeDecl)
        {
            foreach (var instanceConstructor in typeSymbol.InstanceConstructors)
            {
                var ctor = new ConstructorDeclaration()
                {
                    name = instanceConstructor.Name,

                };

                ctor.Items1.AddRange(instanceConstructor.ToModelAccessibility());
                foreach (var param in instanceConstructor.Parameters)
                {
                    ctor.Items2.Add(new VariableDeclaration()
                    {
                        name = param.Name,
                        type1 = param.Type.ToDisplayString()
                    });
                }
                attTypeDecl.Constructors.Add(ctor);
            }
        }

        /// <summary>
        /// extract named and positional agruments
        /// </summary>
        /// <param name="att"></param>
        /// <param name="usage"></param>
        private void ExtractOtherAttributeData(AttributeTypeDeclaration attTypeDecl, 
            AttributeData usage)
        {
            Attribute attrib1 = new Attribute();
            attrib1.name = usage.AttributeClass.Name;
            foreach (var na in usage.NamedArguments)
            {
                var nan = new NamedAttributeArgument()
                {
                    name = na.Key,
                    value = na.Value.ToString()
                };
                attrib1.NamedAttributeArguments.Add(nan);
            }
            foreach (var na in usage.ConstructorArguments)
            {
                var paa = new PositionalAttributeArgument()
                {
                    value = na.Value.ToString()
                };
                attrib1.PositionalAttributeArguments.Add(paa);
            }
            attTypeDecl.Attributes.Add(attrib1);
            csharpModel.Items.Add(attrib1);
        }

        /// <summary>
        /// extracts usage atributes allowmultiple and inherited
        /// </summary>
        /// <param name="attTypeDecl">ecore attribute declaration</param>
        /// <param name="usage">attribute attached to the attribute declaration</param>
        private static void ExtractUsageAttributeData(AttributeTypeDeclaration attTypeDecl, AttributeData usage)
        {
            /// Enum.GetName(typeof(AttributeTargets), ((int)usage.ConstructorArguments.First().Value).ToString());
            attTypeDecl.Items3.Add(AttributeTarget.field);

            var allowMultiple = usage.NamedArguments.FirstOrDefault(x => x.Key == "AllowMultiple").Value.Value;
            if (allowMultiple != null)
            {
                attTypeDecl.allowMultiple = (bool)allowMultiple;
                attTypeDecl.allowMultipleSpecified = true;
            }
            var inherited = usage.NamedArguments.FirstOrDefault(x => x.Key == "Inherited").Value.Value;
            if (inherited != null)
            {
                attTypeDecl.Inherited = (bool)inherited;
                attTypeDecl.InheritedSpecified = true;
            }
        }

        /// <summary>
        /// Extracts and serialize all c# attributes definition contained in a project source
        /// </summary>
        /// <param name="source">csproject path</param>
        /// <param name="destination">file name where the attributes are serialized</param>
        internal void Extract(string source, string destination)
        {
            var workspace = Workspace.LoadStandAloneProject(source);
            var solution = workspace.CurrentSolution;
            foreach (var project in solution.Projects)
            {
                var compilation = project.GetCompilation();

                var attTypeSymbol = compilation.GetTypeByMetadataName("System.Attribute");

                foreach (var document in project.Documents)
                {
                    if (document.LanguageServices.Language == LanguageNames.CSharp)
                    {
                        // semantic model
                        var _semanticModel = document.GetSemanticModel();
                        // syntax model
                        var tree = document.GetSyntaxTree().GetRoot() as SyntaxNode;
                        // get semantic model of all class declarations
                        var allClasses = from _class in tree.DescendantNodes().OfType<ClassDeclarationSyntax>()
                            select _semanticModel.GetDeclaredSymbol(_class) as INamedTypeSymbol;
                        // get semantic model of attribute class
                        var attClasses = from _class in allClasses where _class.HasAsBase(attTypeSymbol) select _class;

                        foreach (var attClass in attClasses)
                        {
                           ExtractAttributeDeclaration (attClass);
                        }
                    }
                }
            }
            csharpModel.SaveToFile(destination);
            Console.WriteLine("NUMP2CS XMI Model successfully created: " + destination);
        }
    }
}
